package org.liang.transfer.ftp;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.liang.common.utils.io.FileUtil;
import org.liang.transfer.exception.UncorrectedRemotePathException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * FTP下载器实现.
 * 
 * <pre>
 * 整合FTP下载实现多有代码。
 * 此类仅向{@link FTP}开放访问
 * </pre>
 * 
 * @author L.Yang
 * @version v1.0 2016年4月11日 下午1:59:24
 */
class FTPDownloader {

    private static final Logger LOGGER = LoggerFactory.getLogger(FTPDownloader.class);

    private FTPClient ftp;

    private OutputStream out = null;

    FTPDownloader(FTPClient ftp) {
        super();
        this.ftp = ftp;
    }

    /**
     * 下载远程服务器上的文件，并存储到本地.
     * 
     * <pre>
     * remote 如果是文件夹则将对文件夹中的文件进行filter过滤，将符合filter的文件下载；
     * 如果local 是文件名则使用locl 如果local 是路径则使用服务器文件名；
     * 过滤规则： 远程文件命中包含filter字符串。
     * tips： 文件夹必须包含结束字符串，否则认定为文件 。在文件目录中过滤符合名称的文件进行下载
     * </pre>
     * 
     * @param remote
     *            远程FTP服务器文件的完整路径
     * @param local
     *            本地存储文件的完整路径
     * @param filter
     *            过滤内容
     * @throws UncorrectedRemotePathException
     *             当远程路径不存在是抛出此异常
     */
    void download(String remote, String local, String filter) {
        try {
            changeWorkingDirectory(remote);

            String remotename = FilenameUtils.getName(remote);
            FTPFile[] files = ftp.listFiles();
            for (FTPFile file : files) {
                if (StringUtils.isNotEmpty(remotename)) {
                    String filename = file.getName();
                    if (!filename.contains(remotename)) {
                        continue;
                    }
                }

                recursivedownload(local, file, filter);
            }
        } catch (IOException e) {
            LOGGER.error("FTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    /**
     * 变更工作路径.
     * 
     * <pre>
     * 如果路径不存在则抛出 {@link UncorrectedRemotePathException}
     * </pre>
     * 
     * @param remote
     *            远程路径
     * @throws IOException
     *             - If an I/O error occurs while either sending a command to the server or
     *             receiving a reply from the server.
     * @see {@link FTPClient#changeWorkingDirectory(String)}
     */
    private void changeWorkingDirectory(String remote) throws IOException {
        String workingDirectory = FilenameUtils.getFullPath(remote);
        boolean isCorrectedPath = ftp.changeWorkingDirectory(workingDirectory);
        if (!isCorrectedPath) {
            throw new UncorrectedRemotePathException(workingDirectory);
        }
        LOGGER.trace("进入工作目录 : {}", new Object[] {workingDirectory});
    }

    /**
     * 递归下载.
     * 
     * <pre>
     * 根据名称判断是否是文件夹：
     *  1. 如果是文件夹则递归下载；
     *  2. 如果不是则进行过滤下载。
     * </pre>
     * 
     * @param out
     *            输出流
     * @param local
     *            本地路径
     * @param file
     *            ftp文件
     * @param filter
     *            过滤字符串
     * @throws IOException
     *             - If an I/O error occurs while either sending a command to the server or
     *             receiving a reply from the server.
     * @see {@link FTPClient#changeWorkingDirectory(String)}
     */
    private void recursivedownload(String local, FTPFile file, String filter)
                    throws IOException {
        String filename = file.getName();
        if (file.isDirectory()) {
            ftp.changeWorkingDirectory(filename);
            LOGGER.trace("进入子文件夹： {}", filename);

            String newloaclpath = generateLocalPath(local, filename);

            FTPFile[] files = ftp.listFiles();
            for (FTPFile ftpfile : files) {
                recursivedownload(newloaclpath, ftpfile, filter);
            }

            ftp.changeToParentDirectory(); // 下载完成后返回上级目录
        } else {
            retrieveFile(local, filename, filter);
        }
    }

    /**
     * 获取文件远程文件.
     * 
     * @param out
     *            输出流
     * @param local
     *            本地文件路径
     * @param filename
     *            远程文件名称
     * @param filter
     *            过滤字符串
     * @throws IOException
     *             - If an I/O error occurs while either sending a command to the server or
     *             receiving a reply from the server.
     * @see {@link FTPClient#retrieveFile(String, OutputStream) }
     */
    private void retrieveFile(String local, String filename, String filter)
                    throws FileNotFoundException, IOException {
        if (StringUtils.isNotEmpty(filter) && !filename.contains(filter)) {
            return;
        }

        FileUtil.createDir(local); // 创建本地文件夹

        String localFilename = generateLocalFilename(local, filename);
        out = new FileOutputStream(FileUtil.createFile(localFilename));

        ftp.retrieveFile(filename, out);

        out.flush();
        IOUtils.closeQuietly(out);

        LOGGER.trace("文件下载完成 ： {}", filename);
    }

    /**
     * 生成本地路径.
     * 
     * <pre>
     * 根据远程服务器的文件路径生成本地路径
     * </pre>
     * 
     * @param local
     *            本地路径
     * @param remotepath
     *            远程路径
     * @return 本地完整路径
     */
    private String generateLocalPath(String local, String filename) {
        return FilenameUtils.getFullPath(local) + File.separator + filename + File.separator;
    }

    /**
     * 生成本地文件名.
     * 
     * <pre>
     * 如果本地文件名中包含文件名，则使用本地文件名，
     * 如果没有文件名则使用远程文件名。
     * </pre>
     * 
     * @param local
     *            本地路径（文件/文件夹）
     * @param filename
     *            远程文件名
     * @return 本地文件绝对路径
     */
    private String generateLocalFilename(String local, String filename) {
        String localpath = FilenameUtils.getFullPath(local);
        String localname = FilenameUtils.getName(local);
        String localFilename = localpath + File.separator
                               + (StringUtils.isEmpty(localname) ? filename : localname);
        return localFilename;
    }
}
